package why.demo.list.exception;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 网关全局异常处理器
 * <p>
 * 处理网关层面的异常，统一错误响应格式。
 *
 * @author why
 * @description 网关异常统一处理
 * @date 2025/9/24 10:00
 */
@Slf4j
@Order(-1)  // 确保在默认异常处理器之前执行
@Component
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {

    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 处理异常
     * <p>
     * 统一处理网关异常，返回标准错误响应
     *
     * @param exchange Web交换对象
     * @param ex       异常对象
     * @return 异常处理结果
     */
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();

        // 确定HTTP状态码
        HttpStatus status = determineStatus(ex);
        response.setStatusCode(status);

        // 设置响应内容类型
        response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE);

        // 构建错误响应体
        Map<String, Object> errorResponse = buildErrorResponse(status, ex);

        // 记录异常日志
        logException(exchange, ex, status);

        try {
            // 序列化错误响应
            String responseBody = objectMapper.writeValueAsString(errorResponse);
            DataBuffer buffer = response.bufferFactory().wrap(responseBody.getBytes(StandardCharsets.UTF_8));

            return response.writeWith(Mono.just(buffer));

        } catch (JsonProcessingException e) {
            log.error("序列化错误响应失败", e);

            // 返回简单的错误信息
            String fallbackResponse = "{\"error\":\"Internal Server Error\",\"message\":\"序列化响应失败\"}";
            DataBuffer buffer = response.bufferFactory().wrap(fallbackResponse.getBytes(StandardCharsets.UTF_8));

            return response.writeWith(Mono.just(buffer));
        }
    }


    /**
     * 确定HTTP状态码
     * <p>
     * 根据异常类型确定合适的HTTP状态码
     *
     * @param ex 异常对象
     * @return HTTP状态码
     */
    private HttpStatus determineStatus(Throwable ex) {
        if (ex instanceof ResponseStatusException) {
            return (HttpStatus) ((ResponseStatusException) ex).getStatusCode();
        }

        // 根据异常类型确定状态码
        String exceptionName = ex.getClass().getSimpleName();
        switch (exceptionName) {
            case "ConnectTimeoutException":
            case "ReadTimeoutException":
                return HttpStatus.GATEWAY_TIMEOUT;
            case "ConnectException":
                return HttpStatus.BAD_GATEWAY;
            case "UnknownHostException":
                return HttpStatus.SERVICE_UNAVAILABLE;
            default:
                return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }

    /**
     * 构建错误响应体
     * <p>
     * 创建统一格式的错误响应信息
     *
     * @param status HTTP状态码
     * @param ex     异常对象
     * @return 错误响应Map
     */
    private Map<String, Object> buildErrorResponse(HttpStatus status, Throwable ex) {
        Map<String, Object> errorResponse = new HashMap<>();

        errorResponse.put("timestamp", System.currentTimeMillis());
        errorResponse.put("status", status.value());
        errorResponse.put("error", status.getReasonPhrase());

        // 根据异常类型设置友好的错误消息
        String message = getErrorMessage(ex);
        errorResponse.put("message", message);

        // 在开发环境可以添加详细的异常信息
        // if (isDevelopmentMode()) {
        //     errorResponse.put("exception", ex.getClass().getSimpleName());
        //     errorResponse.put("trace", getStackTrace(ex));
        // }

        return errorResponse;
    }

    /**
     * 获取友好的错误消息
     * <p>
     * 将技术异常转换为用户友好的错误消息
     *
     * @param ex 异常对象
     * @return 错误消息
     */
    private String getErrorMessage(Throwable ex) {
        String exceptionName = ex.getClass().getSimpleName();

        switch (exceptionName) {
            case "ConnectTimeoutException":
                return "服务连接超时，请稍后重试";
            case "ReadTimeoutException":
                return "服务响应超时，请稍后重试";
            case "ConnectException":
                return "无法连接到服务，请检查网络或联系管理员";
            case "UnknownHostException":
                return "服务暂时不可用，请稍后重试";
            case "ResponseStatusException":
                return ex.getMessage();
            default:
                return "服务内部错误，请联系管理员";
        }
    }

    /**
     * 记录异常日志
     * <p>
     * 记录详细的异常信息，便于问题排查
     *
     * @param exchange Web交换对象
     * @param ex       异常对象
     * @param status   HTTP状态码
     */
    private void logException(ServerWebExchange exchange, Throwable ex, HttpStatus status) {
        String uri = exchange.getRequest().getURI().toString();
        String clientIp = getClientIp(exchange);

        log.error("网关异常处理 -  {} from {} - Status: {} - Error: {}",
                uri, clientIp, status.value(), ex.getMessage(), ex);
    }

    /**
     * 获取客户端IP地址
     *
     * @param exchange Web交换对象
     * @return 客户端IP
     */
    private String getClientIp(ServerWebExchange exchange) {
        String xForwardedFor = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }

        String xRealIp = exchange.getRequest().getHeaders().getFirst("X-Real-IP");
        if (xRealIp != null && !xRealIp.isEmpty()) {
            return xRealIp;
        }

        return exchange.getRequest().getRemoteAddress() != null ?
                exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() : "unknown";
    }
}
